home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-19 / gpt32src.zip / HELP.C < prev    next >
C/C++ Source or Header  |  1992-03-25  |  19KB  |  681 lines

  1. #ifndef lint
  2. static char *RCSid = "$Id: help.c,v 3.26 92/03/24 22:34:24 woo Exp Locker: woo $";
  3. #endif
  4.  
  5. /* GNUPLOT - help.c */
  6. /*
  7.  * Copyright (C) 1986, 1987, 1990, 1991, 1992   Thomas Williams, Colin Kelley
  8.  *
  9.  * Permission to use, copy, and distribute this software and its
  10.  * documentation for any purpose with or without fee is hereby granted, 
  11.  * provided that the above copyright notice appear in all copies and 
  12.  * that both that copyright notice and this permission notice appear 
  13.  * in supporting documentation.
  14.  *
  15.  * Permission to modify the software is granted, but not the right to
  16.  * distribute the modified code.  Modifications are to be distributed 
  17.  * as patches to released version.
  18.  *  
  19.  * This software is provided "as is" without express or implied warranty.
  20.  * 
  21.  *
  22.  * AUTHORS
  23.  * 
  24.  *   Original Software:
  25.  *     Thomas Williams,  Colin Kelley.
  26.  * 
  27.  *   Gnuplot 2.0 additions:
  28.  *       Russell Lang, Dave Kotz, John Campbell.
  29.  *
  30.  *   Gnuplot 3.0 additions:
  31.  *       Gershon Elber and many others.
  32.  * 
  33.  * Send your comments or suggestions to 
  34.  *  info-gnuplot@ames.arc.nasa.gov.
  35.  * This is a mailing list; to join it send a note to 
  36.  *  info-gnuplot-request@ames.arc.nasa.gov.  
  37.  * Send bug reports to
  38.  *  bug-gnuplot@ames.arc.nasa.gov.
  39.  */
  40.  
  41. #include <stdio.h>
  42.  
  43. extern int errno;
  44.  
  45. extern int strcmp();
  46. extern int strlen();
  47. extern char *strcpy();
  48. extern char *strncpy();
  49. extern char *strcat();
  50. extern char *strncat();
  51. extern char *getenv();
  52. extern FILE *fopen();
  53. extern char *malloc();
  54.  
  55. extern int instring();
  56.  
  57. #define    SAME    0    /* for strcmp() */
  58.  
  59. #include "help.h"    /* values passed back */
  60.  
  61. /* help -- help subsystem that understands defined keywords
  62. **
  63. ** Looks for the desired keyword in the help file at runtime, so you
  64. ** can give extra help or supply local customizations by merely editing
  65. ** the help file.
  66. **
  67. ** The original (single-file) idea and algorithm is by John D. Johnson,
  68. ** Hewlett-Packard Company.  Thanx and a tip of the Hatlo hat!
  69. **
  70. ** Much extension by David Kotz for use in gnutex, and then in gnuplot.
  71. ** Added output paging support, both unix and builtin. Rewrote completely
  72. ** to read helpfile into memory, avoiding reread of help file. 12/89.
  73. **
  74. ** Modified by Russell Lang to avoid reading completely into memory
  75. ** if MSDOS defined.  This uses much less memory.  6/91
  76. **
  77. ** The help file looks like this (the question marks are really in column 1):
  78. **
  79. **     ?topic
  80. **     This line is printed when the user wants help on "topic".
  81. **     ?keyword
  82. **     ?Keyword
  83. **     ?KEYWORD
  84. **     These lines will be printed on the screen if the user wanted
  85. **     help on "keyword", "Keyword", or "KEYWORD".  No casefolding is
  86. **    done on the keywords.
  87. **     ?subject
  88. **     ?alias
  89. **     This line is printed for help on "subject" and "alias".
  90. **     ?
  91. **    ??
  92. **     Since there is a null keyword for this line, this section
  93. **     is printed when the user wants general help (when a help
  94. **     keyword isn't given).  A command summary is usually here.
  95. **    Notice that the null keyword is equivalent to a "?" keyword
  96. **    here, because of the '?' and '??' topic lines above.
  97. **   If multiple keywords are given, the first is considered the 
  98. **   'primary' keyword. This affects a listing of available topics.
  99. **     ?last-subject
  100. **     Note that help sections are terminated by the start of the next
  101. **     '?' entry or by EOF.  So you can't have a leading '?' on a line
  102. **     of any help section.  You can re-define the magic character to
  103. **    recognize in column 1, though, if '?' is too useful.  (Try ^A.)
  104. */
  105.  
  106. #define    KEYFLAG    '?'    /* leading char in help file topic lines */
  107.  
  108. /*
  109. ** Calling sequence:
  110. **    int result;        # 0 == success
  111. **    char *keyword;        # topic to give help on
  112. **    char *pathname;        # path of help file
  113. **      int subtopics;        # set to TRUE if only subtopics to be listed
  114. **                # returns TRUE if subtopics were found
  115. **    result = help(keyword, pathname, &subtopics);
  116. ** Sample:
  117. **    cmd = "search\n";
  118. **    helpfile = "/usr/local/lib/program/program.help";
  119. **    subtopics = FALSE;
  120. **    if (help(cmd, helpfile, &subtopics) != H_FOUND)
  121. **        printf("Sorry, no help for %s", cmd);
  122. **
  123. **
  124. ** Speed this up by replacing the stdio calls with open/close/read/write.
  125. */
  126. #ifdef    WDLEN
  127. #  define    PATHSIZE    WDLEN
  128. #else
  129. #  define    PATHSIZE    BUFSIZ
  130. #endif
  131.  
  132. typedef int boolean;
  133. #ifndef TRUE
  134. #define TRUE (1)
  135. #define FALSE (0)
  136. #endif
  137.  
  138. typedef struct line_s LINEBUF;
  139. struct line_s {
  140.     char *line;            /* the text of this line */
  141.     LINEBUF *next;            /* the next line */
  142. };
  143.  
  144. typedef struct linkey_s LINKEY;
  145. struct linkey_s {
  146.     char *key;                /* the name of this key */
  147.     long pos;                /* ftell position */
  148.     LINEBUF *text;            /* the text for this key */
  149.     boolean primary;        /* TRUE -> is a primary name for a text block */
  150.     LINKEY *next;            /* the next key in linked list */
  151. };
  152.  
  153. typedef struct key_s KEY;
  154. struct key_s {
  155.     char *key;                /* the name of this key */
  156.     long pos;                /* ftell position */
  157.     LINEBUF *text;            /* the text for this key */
  158.     boolean primary;        /* TRUE -> is a primary name for a text block */
  159. };
  160. static LINKEY *keylist = NULL;    /* linked list of keys */
  161. static KEY *keys = NULL;        /* array of keys */
  162. static int keycount = 0;        /* number of keys */
  163. static FILE *helpfp = NULL;
  164.  
  165. static int LoadHelp();
  166. static void sortkeys();
  167. static int keycomp();
  168. static LINEBUF *storeline();
  169. static LINKEY *storekey();
  170. static KEY *FindHelp();
  171. static boolean Ambiguous();
  172.  
  173. /* Help output */
  174. static void PrintHelp();
  175. static void ShowSubtopics();
  176. static void StartOutput();
  177. static void OutLine();
  178. static void EndOutput();
  179. static FILE *outfile;        /* for unix pager, if any */
  180. static int pagelines;        /* count for builtin pager */
  181. #define SCREENSIZE 24        /* lines on screen (most have at least 24) */
  182.  
  183. /* help:
  184.  * print a help message 
  185.  * also print available subtopics, if subtopics is TRUE
  186.  */
  187. help(keyword, path, subtopics)
  188.     char *keyword;        /* on this topic */
  189.     char *path;            /* from this file */
  190.     boolean *subtopics;    /* (in) - subtopics only? */
  191.                         /* (out) - are there subtopics? */
  192. {
  193.     static char oldpath[PATHSIZE] = "";    /* previous help file */
  194.     int status;            /* result of LoadHelp */
  195.     KEY *key;            /* key that matches keyword */
  196.  
  197.     /*
  198.     ** Load the help file if necessary (say, first time we enter this routine,
  199.     ** or if the help file changes from the last time we were called).
  200.     ** Also may occur if in-memory copy was freed.
  201.     ** Calling routine may access errno to determine cause of H_ERROR.
  202.     */
  203.     errno = 0;
  204.     if (strncmp(oldpath, path, PATHSIZE) != SAME)
  205.      FreeHelp();
  206.     if (keys == NULL) {
  207.        status = LoadHelp(path);
  208.        if (status == H_ERROR)
  209.         return(status);
  210.  
  211.        /* save the new path in oldpath */
  212.        if (strlen(path) < PATHSIZE)
  213.         (void) strcpy(oldpath, path);
  214.        else {                /* not enough room in oldpath, sigh */
  215.           (void) strncpy(oldpath, path, PATHSIZE - 1);
  216.           oldpath[PATHSIZE - 1] = '\0';
  217.        }
  218.     }
  219.  
  220.     /* look for the keyword in the help file */
  221.     key = FindHelp(keyword);
  222.     if (key != NULL) {
  223.        /* found the keyword: print help and return */
  224.        PrintHelp(key, subtopics);
  225.        status = H_FOUND;
  226.     } else {
  227.        status = H_NOTFOUND;
  228.     }
  229.  
  230.     return(status);
  231. }
  232.  
  233. /* we only read the file once, into memory
  234.  * except for MSDOS when we don't read all the file -
  235.  * just the keys and location of the text
  236.  */
  237. static int
  238. LoadHelp(path)
  239.     char *path;
  240. {
  241.     LINKEY *key;            /* this key */
  242.     long pos;                /* ftell location within help file */
  243.     char buf[BUFSIZ];        /* line from help file */
  244.     LINEBUF *head;            /* head of text list  */
  245.     LINEBUF *firsthead = NULL;
  246.     boolean primary;        /* first ? line of a set is primary */
  247.     boolean flag;
  248.  
  249.     if ((helpfp = fopen(path, "r")) == NULL) {
  250.        /* can't open help file, so error exit */
  251.        return (H_ERROR);
  252.     }
  253.  
  254.     /*
  255.     ** The help file is open.  Look in there for the keyword.
  256.     */
  257.     (void) fgets(buf, BUFSIZ - 1, helpfp);
  258.     while (!feof(helpfp)) {
  259.        /*
  260.         ** Make an entry for each synonym keyword
  261.         */
  262.        primary = TRUE;
  263.        while (buf[0] == KEYFLAG) {
  264.           key = storekey(buf+1);    /* store this key */
  265.           key->primary = primary;
  266.           key->text = NULL;            /* fill in with real value later */
  267.           key->pos = 0;                /* fill in with real value later */
  268.           primary = FALSE;
  269.           pos = ftell(helpfp);
  270.           if (fgets(buf, BUFSIZ - 1, helpfp) == (char *)NULL)
  271.             break;
  272.        }
  273.        /*
  274.         ** Now store the text for this entry.
  275.         ** buf already contains the first line of text.
  276.         */
  277. #ifndef MSDOS
  278.        firsthead = storeline(buf);
  279.        head = firsthead;
  280. #endif
  281.        while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  282.         && (buf[0] != KEYFLAG) ){
  283. #ifndef MSDOS
  284.           /* save text line */
  285.           head->next = storeline(buf);
  286.           head = head->next;
  287. #endif
  288.        }
  289.        /* make each synonym key point to the same text */
  290.        do {
  291.           key->pos = pos;
  292.           key->text = firsthead;
  293.           flag = key->primary;
  294.           key = key->next;
  295.        } while ( flag!=TRUE  &&  key!=NULL );
  296.     }
  297. #ifndef MSDOS
  298.     (void) fclose(helpfp);
  299. #endif
  300.  
  301.     /* we sort the keys so we can use binary search later */
  302.     sortkeys();
  303.     return(H_FOUND); /* ok */
  304. }
  305.  
  306. /* make a new line buffer and save this string there */
  307. static LINEBUF *
  308. storeline(text)
  309.     char *text;
  310. {
  311.     LINEBUF *new;
  312.  
  313.     new = (LINEBUF *)malloc(sizeof(LINEBUF));
  314.     if (new == NULL)
  315.      int_error("not enough memory to store help file", -1);
  316.     if (text != NULL) {
  317.        new->line = (char *) malloc((unsigned int)(strlen(text)+1));
  318.        if (new->line == NULL)
  319.         int_error("not enough memory to store help file", -1);
  320.        (void) strcpy(new->line, text);
  321.     } else
  322.      new->line = NULL;
  323.  
  324.     new->next = NULL;
  325.  
  326.     return(new);
  327. }
  328.  
  329. /* Add this keyword to the keys list, with the given text */
  330. static LINKEY *
  331. storekey(key)
  332.     char *key;
  333. {
  334.     LINKEY *new;
  335.  
  336.     key[strlen(key)-1] = '\0'; /* cut off \n  */
  337.  
  338.     new = (LINKEY *)malloc(sizeof(LINKEY));
  339.     if (new == NULL)
  340.      int_error("not enough memory to store help file", -1);
  341.     new->key = (char *) malloc((unsigned int)(strlen(key)+1));
  342.     if (new->key == NULL)
  343.      int_error("not enough memory to store help file", -1);
  344.     (void) strcpy(new->key, key);
  345.  
  346.     /* add to front of list */
  347.     new->next = keylist;
  348.     keylist = new;
  349.     keycount++;
  350.     return(new);
  351. }
  352.  
  353. /* we sort the keys so we can use binary search later */
  354. /* We have a linked list of keys and the number.
  355.  * to sort them we need an array, so we reform them into an array,
  356.  * and then throw away the list.
  357.  */
  358. static void
  359. sortkeys()
  360. {
  361.     LINKEY *p,*n;            /* pointers to linked list */
  362.     int i;                /* index into key array */
  363.     
  364.     /* allocate the array */
  365.     keys = (KEY *)malloc((unsigned int)((keycount+1) * sizeof(KEY)));
  366.     if (keys == NULL)
  367.      int_error("not enough memory to store help file", -1);
  368.     
  369.     /* copy info from list to array, freeing list */
  370.     for (p = keylist, i = 0; p != NULL; p = n, i++) {
  371.        keys[i].key = p->key;
  372.        keys[i].pos = p->pos;
  373.        keys[i].text = p->text;
  374.        keys[i].primary = p->primary;
  375.        n = p->next;
  376.        free( (char *)p );
  377.     }
  378.  
  379.     /* a null entry to terminate subtopic searches */
  380.     keys[keycount].key = NULL;
  381.     keys[keycount].pos = 0;
  382.     keys[keycount].text = NULL;
  383.  
  384.     /* sort the array */
  385.     /* note that it only moves objects of size (two pointers + long + int) */
  386.     /* it moves no strings */
  387.     qsort((char *)keys, keycount, sizeof(KEY), keycomp);
  388. }
  389.  
  390. static int
  391. keycomp(a, b)
  392.     KEY *a,*b;
  393. {
  394.     return (strcmp(a->key, b->key));
  395. }
  396.  
  397. /* Free the help file from memory. */
  398. /* May be called externally if space is needed */
  399. void
  400. FreeHelp()
  401. {
  402.     int i;                /* index into keys[] */
  403.     LINEBUF *t, *next;
  404.  
  405.     if (keys == NULL)
  406.      return;
  407.  
  408.     for (i = 0; i < keycount; i++) {
  409.        free( (char *)keys[i].key );
  410.        if (keys[i].primary)   /* only try to release text once! */
  411.        for (t = keys[i].text; t != NULL; t = next) {
  412.           free( (char *)t->line );
  413.           next = t->next;
  414.           free( (char *)t );
  415.        }
  416.     }
  417.     free( (char *)keys );
  418.     keys = NULL;
  419.     keycount = 0;
  420. #ifdef MSDOS
  421.     (void) fclose(helpfp);
  422. #endif
  423. }
  424.  
  425. /* FindHelp:
  426.  *  Find the key that matches the keyword.
  427.  *  The keys[] array is sorted by key.
  428.  *  We could use a binary search, but a linear search will aid our
  429.  *  attempt to allow abbreviations. We search for the first thing that
  430.  *  matches all the text we're given. If not an exact match, then
  431.  *  it is an abbreviated match, and there must be no other abbreviated
  432.  *  matches -- for if there are, the abbreviation is ambiguous. 
  433.  *  We print the ambiguous matches in that case, and return not found.
  434.  */
  435. static KEY *                /* NULL if not found */
  436. FindHelp(keyword)
  437.     char *keyword;            /* string we look for */
  438. {
  439.     KEY *key;
  440.     int len = strlen(keyword);
  441.     int compare;
  442.  
  443.     for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
  444.        compare = strncmp(keyword, key->key, len);
  445.        if (compare == 0)    /* we have a match! */
  446.         if (!Ambiguous(key, len)) {
  447.             /* non-ambiguous abbreviation */
  448.             (void) strcpy(keyword, key->key); /* give back the full spelling */
  449.             return(key);        /* found!! */
  450.         }
  451.     }
  452.  
  453.     /* not found, or ambiguous */
  454.     return(NULL);
  455. }
  456.  
  457. /* Ambiguous:
  458.  * Check the key for ambiguity up to the given length.
  459.  * It is ambiguous if it is not a complete string and there are other
  460.  * keys following it with the same leading substring.
  461.  */
  462. static boolean
  463. Ambiguous(key, len)
  464.     KEY *key;
  465.     int len;
  466. {
  467.     char *first;
  468.     char *prev;
  469.     boolean status = FALSE;    /* assume not ambiguous */
  470.     int compare;
  471.     int sublen;
  472.  
  473.     if (key->key[len] == '\0')
  474.      return(FALSE);
  475.     
  476.     for (prev = first = key->key, compare = 0, key++;
  477.         key->key != NULL && compare == 0; key++) {
  478.        compare = strncmp(first, key->key, len);
  479.        if (compare == 0) {
  480.           /* So this key matches the first one, up to len.
  481.            * But is it different enough from the previous one
  482.            * to bother printing it as a separate choice?
  483.            */
  484.           sublen = instring(prev+len, ' ');
  485.           if (strncmp(key->key, prev, len+sublen) != 0) {
  486.              /* yup, this is different up to the next space */
  487.              if (!status) {
  488.                 /* first one we have printed is special */
  489.                 fprintf(stderr, 
  490.                        "Ambiguous request '%.*s'; possible matches:\n",
  491.                        len, first);
  492.                 fprintf(stderr, "\t%s\n", prev);
  493.                 status = TRUE;
  494.              }
  495.              fprintf(stderr, "\t%s\n", key->key);
  496.              prev = key->key;
  497.           }
  498.        }
  499.     }
  500.     
  501.     return(status);
  502. }
  503.  
  504. /* PrintHelp:
  505.  * print the text for key
  506.  */
  507. static void
  508. PrintHelp(key, subtopics)
  509.     KEY *key;
  510.     boolean *subtopics;        /* (in) - subtopics only? */
  511.                         /* (out) - are there subtopics? */
  512. {
  513.     LINEBUF *t;
  514. #ifdef MSDOS
  515.     char buf[BUFSIZ];        /* line from help file */
  516. #endif
  517.  
  518.     StartOutput();
  519.  
  520.     if (subtopics == NULL || !*subtopics) {
  521. #ifdef MSDOS
  522.        fseek(helpfp,key->pos,0);
  523.        while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  524.             && (buf[0] != KEYFLAG) ) {
  525.           OutLine(buf);
  526.        }
  527. #else
  528.        for (t = key->text; t != NULL; t = t->next)
  529.         OutLine(t->line);        /* print text line */
  530. #endif
  531.     }
  532.  
  533.     ShowSubtopics(key, subtopics);
  534.     OutLine("\n");
  535.  
  536.     EndOutput();
  537. }
  538.  
  539. /* ShowSubtopics:
  540.  *  Print a list of subtopic names
  541.  */
  542. #define PER_LINE 4
  543.  
  544. static void
  545. ShowSubtopics(key, subtopics)
  546.     KEY *key;                /* the topic */
  547.     boolean *subtopics;        /* (out) are there any subtopics */
  548. {
  549.     int subt = 0;            /* printed any subtopics yet? */
  550.     KEY *subkey;            /* subtopic key */
  551.     int len;                /* length of key name */
  552.     char line[BUFSIZ];        /* subtopic output line */
  553.     char *start;            /* position of subname in key name */
  554.     int sublen;            /* length of subname */
  555.     int pos;
  556.     char *prev = NULL;        /* the last thing we put on the list */
  557.  
  558.     *line = '\0';
  559.     len = strlen(key->key);
  560.  
  561.     for (subkey = key+1; subkey->key != NULL; subkey++) {
  562.        if (strncmp(subkey->key, key->key, len) == 0) {
  563.           /* find this subtopic name */
  564.           start = subkey->key + len;
  565.           if (len > 0)
  566.             if (*start == ' ')
  567.              start++;        /* skip space */
  568.             else
  569.              break;        /* not the same topic after all  */
  570.           else            /* here we are looking for main topics */
  571.             if (!subkey->primary)
  572.              continue;    /* not a main topic */
  573.           sublen = instring(start, ' ');
  574.           if (prev == NULL || strncmp(start, prev, sublen) != 0) {
  575.              if (subt == 0) {
  576.                 subt++;
  577.                 if (len)
  578.                   (void) sprintf(line, "\nSubtopics available for %s:\n", 
  579.                         key->key);
  580.                 else
  581.                   (void) sprintf(line, "\nHelp topics available:\n");
  582.                 OutLine(line);
  583.                 *line = '\0';
  584.                 pos = 0;
  585.              }
  586.              if (pos == PER_LINE) {
  587.                 (void) strcat(line, "\n");
  588.                 OutLine(line);
  589.                 *line = '\0';
  590.                 pos = 0;
  591.              }
  592.              (void) strcat(line, "\t");
  593.              (void) strncat(line, start, sublen);
  594.              pos++;
  595.              prev = start;
  596.           }
  597.        } else {
  598.           /* new topic */
  599.           break;
  600.        }
  601.     }
  602.     
  603.     /* put out the last line */
  604.     if (subt > 0 && pos > 0) {
  605.        (void) strcat(line, "\n");
  606.        OutLine(line);
  607.     }
  608.     
  609. /*
  610.     if (subt == 0) {
  611.        OutLine("\n");
  612.        OutLine("No subtopics available\n");
  613.     }
  614. */
  615.     
  616.     if (subtopics)
  617.      *subtopics = (subt != 0);
  618. }
  619.  
  620.  
  621. /* StartOutput:
  622.  * Open a file pointer to a pipe to user's $PAGER, if there is one,
  623.  * otherwise use our own pager.
  624.  */
  625. static void
  626. StartOutput()
  627. {
  628. #ifdef unix
  629.     char *pager_name = getenv("PAGER");
  630.     extern FILE *popen();
  631.  
  632.     if (pager_name != NULL && *pager_name != '\0')
  633.      if ((outfile = popen(pager_name, "w")) != (FILE *)NULL)
  634.        return;            /* success */
  635.     outfile = stderr;
  636.     /* fall through to built-in pager */
  637. #endif
  638.  
  639.     /* built-in pager */
  640.     pagelines = 0;
  641. }
  642.  
  643. /* write a line of help output  */
  644. /* line should contain only one \n, at the end */
  645. static void
  646. OutLine(line)
  647.     char *line;
  648. {
  649.     int c;                /* dummy input char */
  650. #ifdef unix
  651.     if (outfile != stderr) {
  652.        fputs(line, outfile);
  653.        return;
  654.     }
  655. #endif
  656.  
  657.     /* built-in dumb pager */
  658.     /* leave room for prompt line */
  659.     if (pagelines >= SCREENSIZE - 2) {
  660.        fprintf(stderr,"Press return for more: ");
  661.        do 
  662.         c = getchar();
  663.        while (c != EOF && c != '\n');
  664.        pagelines = 0;
  665.     }
  666.     fputs(line, stderr);
  667.     pagelines++;
  668. }
  669.  
  670. static void
  671. EndOutput()
  672. {
  673. #ifdef unix
  674.     extern int pclose();
  675.  
  676.     if (outfile != stderr)
  677.      (void) pclose(outfile);
  678. #endif
  679. }
  680.  
  681.